/*
 * 
 *	Copyright (C) 2011 -- Alfred Reibenschuh
 *
 * 	This program is free software; you can redistribute it and/or 
 *	modify it under the terms of the GNU General Public License as 
 *	published by the Free Software Foundation; either version 2 of 
 *	the License, or (at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful, 
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of 
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 *	GNU General Public License for more details.
 * 
 */

package redoak.php.ldap;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Hashtable;
import java.util.Vector;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapName;

public class JndiLdapSession implements ILdapSession
{
	int port;
	String host;
	String schema;
	InitialDirContext ctx;
	
	public JndiLdapSession()
	{
	}

	public boolean bindSasl(String u, String p, String mech)
	{
		String uri = schema+"://"+host+":"+port+"/";
		
		Hashtable env = new Hashtable();
		env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
		env.put(Context.PROVIDER_URL, uri);

		env.put(Context.SECURITY_AUTHENTICATION, mech);
		if(u!=null)
		{
			env.put(Context.SECURITY_PRINCIPAL, u);
			env.put(Context.SECURITY_CREDENTIALS, p);
		}
		
		try 
		{
			ctx = new InitialDirContext(env);
			return true;
		} 
		catch (NamingException e) 
		{
			e.printStackTrace();
		}

		return false;
	}

	public boolean bindSimple(String u, String p)
	{
		String uri = schema+"://"+host+":"+port+"/";
		
		Hashtable env = new Hashtable();
		env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
		env.put(Context.PROVIDER_URL, uri);

		env.put(Context.SECURITY_AUTHENTICATION, "simple");
		env.put(Context.SECURITY_PRINCIPAL, u);
		env.put(Context.SECURITY_CREDENTIALS, p);
		try 
		{
			ctx = new InitialDirContext(env);
			return true;
		} 
		catch (NamingException e) 
		{
			e.printStackTrace();
		}

		return false;
	}

	public boolean bind()
	{
		String uri = schema+"://"+host+":"+port+"/";
		
		Hashtable env = new Hashtable();
		env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
		env.put(Context.PROVIDER_URL, uri);

		env.put(Context.SECURITY_AUTHENTICATION, "none");

		try 
		{
			ctx = new InitialDirContext(env);
			return true;
		} 
		catch (NamingException e) 
		{
			e.printStackTrace();
		}

		return false;
	}

	public void setPort(int port) 
	{
		this.port=port;
	}
	
	public boolean setUri(String hosturi)
	{
		try 
		{
			URI uri = new URI(hosturi);
			this.schema = uri.getScheme();
			this.host = uri.getHost();
			this.port = uri.getPort();
			if(this.port==0 && this.schema.equals("ldaps"))
			{
				this.port = 636;
			}
			else if(this.port==0)
			{
				this.port = 389;
			}
			return true;
		} 
		catch (URISyntaxException e) 
		{
			e.printStackTrace();
		}
		return false;
	}
	
	public void setHost(String hosturi) 
	{
		this.schema="ldap";
		if(hosturi.indexOf(':')>0)
		{
			this.port = Integer.parseInt(hosturi.substring(hosturi.indexOf(':')+1));
			this.host = hosturi.substring(0,hosturi.indexOf(':'));
		}
		else
		{
			this.host = hosturi;
		}
	}
	
	public boolean close() 
	{
		if(ctx!=null)
		{
			try 
			{
				ctx.close();
				ctx=null;
				return true;
			} 
			catch (NamingException e) 
			{
				e.printStackTrace();
			}
		}
		return false;
	}

	private NamedHashtable toTable(Attributes attr)
	{
		try 
		{
			NamedHashtable v = new NamedHashtable();
			NamingEnumeration<String> en = attr.getIDs();
			while(en.hasMoreElements())
			{
				Vector<String> vv = new Vector();
				String k = en.nextElement();
				Attribute va = attr.get(k);
				NamingEnumeration ena = va.getAll();
				while(ena.hasMoreElements())
				{
					vv.add(ena.nextElement().toString());
				}
				v.put(k.toLowerCase(), vv.toArray(new String[]{}));
			}
			return v;
		} 
		catch (NamingException e) 
		{
			e.printStackTrace();
		}
		return null;
	}
	
	public NamedHashtable get(String dn) 
	{
		try 
		{
			Attributes attr = ctx.getAttributes(dn);
			NamedHashtable t = toTable(attr);
			t.setName(dn);
			return t;
		} 
		catch (NamingException e) 
		{
			e.printStackTrace();
		}
		return null;
	}

	public NamedHashtable get(String dn, String filter) 
	{
		SearchControls sc = new SearchControls();
		sc.setCountLimit(1);
		sc.setTimeLimit(5000);
		sc.setReturningAttributes(null);
		sc.setSearchScope(SearchControls.OBJECT_SCOPE);
		sc.setReturningObjFlag(true);
		sc.setDerefLinkFlag(false);

		try 
		{
			NamingEnumeration<SearchResult> ex = ctx.search(dn, filter, sc);
			if(ex.hasMoreElements())
			{
				NamedHashtable v = toTable(ex.nextElement().getAttributes());
				v.setName(dn);
				ex.close();
				return v;
			}
		} 
		catch (NamingException e) 
		{
			e.printStackTrace();
		}
		return null;
	}

	public Vector<NamedHashtable> list(String dn, String filter) 
	{
		SearchControls sc = new SearchControls();
		sc.setCountLimit(1000);
		sc.setTimeLimit(5000);
		sc.setReturningAttributes(null);
		sc.setSearchScope(SearchControls.ONELEVEL_SCOPE);
		sc.setReturningObjFlag(true);
		sc.setDerefLinkFlag(false);

		try 
		{
			Vector<NamedHashtable> v = new Vector();
			NamingEnumeration<SearchResult> ex = ctx.search(dn, filter, sc);
			while(ex.hasMoreElements())
			{
				SearchResult obj = ex.nextElement();
				NamedHashtable vt = toTable(obj.getAttributes());
				vt.setName(obj.getName());
				v.add(vt);
			}
			ex.close();
			return v;
		} 
		catch (NamingException e) 
		{
			e.printStackTrace();
		}
		return null;
	}

	public Vector<NamedHashtable> search(String dn, String filter) 
	{
		SearchControls sc = new SearchControls();
		sc.setCountLimit(1000);
		sc.setTimeLimit(5000);
		sc.setReturningAttributes(null);
		sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
		sc.setReturningObjFlag(true);
		sc.setDerefLinkFlag(false);

		try 
		{
			Vector<NamedHashtable> v = new Vector();
			NamingEnumeration<SearchResult> ex = ctx.search(dn, filter, sc);
			while(ex.hasMoreElements())
			{
				SearchResult obj = ex.nextElement();
				NamedHashtable vt = toTable(obj.getAttributes());
				vt.setName(obj.getName());
				v.add(vt);
			}
			ex.close();
			return v;
		} 
		catch (NamingException e) 
		{
			e.printStackTrace();
		}
		return null;
	}

	public boolean delete(String dn) 
	{
		try 
		{
			ctx.unbind(dn);
			return true;
		} 
		catch (NamingException e) 
		{
			e.printStackTrace();
		}		
		return false;
	}
	
	public boolean add(NamedHashtable ht) 
	{
		try 
		{
			String name = ht.getName();
			Attributes attrs = new BasicAttributes();
			for(Object key : ht.keySet().toArray())
			{
				Attribute attr = new BasicAttribute(key.toString());
				for(String v : ht.get(key))
				{
					attr.add(v);
				}
				attrs.put(attr);
			}
			ctx.createSubcontext(name, attrs);
			return true;
		} 
		catch (NamingException e) 
		{
			e.printStackTrace();
		}
		return false;
	}
	
	public boolean modify(NamedHashtable ht) 
	{
		try 
		{
			String name = ht.getName();
			Vector<ModificationItem> mods = new Vector();
			
			Attributes old_attrs = ctx.getAttributes(name);

			NamingEnumeration<String> en = old_attrs.getIDs();
			while(en.hasMoreElements())
			{
				String k = en.nextElement();
				if(!ht.containsKey(k))
				{
					mods.add(new ModificationItem(ctx.REMOVE_ATTRIBUTE, new BasicAttribute(k)));
				}
			}
			
			for(Object key : ht.keySet().toArray())
			{
				if(old_attrs.get(key.toString())!=null)
				{
					Attribute attr = new BasicAttribute(key.toString());
					for(String v : ht.get(key))
					{
						attr.add(v);
					}
					mods.add(new ModificationItem(ctx.REPLACE_ATTRIBUTE, attr));
				}
			}

			for(Object key : ht.keySet().toArray())
			{
				if(old_attrs.get(key.toString())==null)
				{
					Attribute attr = new BasicAttribute(key.toString());
					for(String v : ht.get(key))
					{
						attr.add(v);
					}
					mods.add(new ModificationItem(ctx.ADD_ATTRIBUTE, attr));
				}
			}
			
			ctx.modifyAttributes(name, mods.toArray(new ModificationItem[]{}));

			return true;
		} 
		catch (NamingException e) 
		{
			e.printStackTrace();
		}
		return false;
	}

	public static void main(String[] args)
	{
		JndiLdapSession ldap = new JndiLdapSession();
		ldap.setHost("directory.verisign.com:389");
		ldap.bind();
		
		//NamedHashtable entry = ldap.get("");
		//namingcontexts
		//for(String n : entry.get("namingcontexts"))
		//{
		//	System.err.println("ctx="+n);
		//}

		NamedHashtable entry = ldap.get("dc=verisign,dc=com");
		System.err.println(entry);
		
		ldap.close();
	}

	public boolean connect() 
	{
		return true;
	}

	public boolean connect(String host, int port) 
	{
		this.host = host;
		this.port = port;
		return true;
	}
}